/*
 * Copyright (C) 2017, 2019 Samuel Wall
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package uk.co.samuelwall.materialtaptargetprompt.extras;

import ohos.agp.render.BlendMode;
import ohos.agp.render.Paint;
import ohos.agp.text.Font;
import ohos.agp.text.Layout;
import ohos.agp.text.SimpleTextLayout;
import ohos.agp.utils.LayoutAlignment;
import ohos.agp.utils.Point;
import ohos.agp.utils.Rect;
import ohos.agp.utils.RectFloat;
import ohos.global.resource.ResourceManager;
import uk.co.samuelwall.materialtaptargetprompt.utils.Constants;

import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;

/**
 * Useful methods for prompts that don't fit else where.
 *
 * @since 2021-06-21
 */
public class PromptUtils {
    private PromptUtils() {
    }

    /**
     * Determines if a point is in the centre of a circle with a radius from the point.
     *
     * @param x The x position in the view.
     * @param y The y position in the view.
     * @param circleCentre The circle centre position
     * @param radius The radius of the circle.
     * @return True if the point (x, y) is in the circle.
     */
    public static boolean isPointInCircle(
            final float x, final float y,
            final Point circleCentre,
            final float radius) {
        return Math.pow(x - circleCentre.getPointX(), Constants.NUM_2)
                + Math.pow(y - circleCentre.getPointY(), Constants.NUM_2) < Math.pow(radius, Constants.NUM_2);
    }

    /**
     * setFont
     *
     * @param textPaint textPaint
     * @param font font
     * @param style style
     */
    public static void setFont(Paint textPaint, @Nullable Font font, int style) {
        if (style > 0 && style < Constants.NUM_3) {
            Font.Builder builder;
            if (font == null) {
                builder = new Font.Builder("");
            } else {
                builder = new Font.Builder(font.getName());
            }
            if (style == 1) {
                builder.setWeight(Font.BOLD);
            } else {
                builder.makeItalic(true);
            }
            Font fontB = builder.build();
            textPaint.setFont(fontB);
            int fontStyle = fontB != null ? style : 0;
            int need = style & ~fontStyle;
            textPaint.setFakeBoldText((need & 1) != 0);
            textPaint.horizontalTilt((need & Constants.NUM_2) != 0 ? Constants.NUM_F025 : 0);
        } else if (font != null) {
            textPaint.setFont(font);
        } else {
            textPaint.setFont(new Font.Builder("").build());
        }
    }

    /**
     * setFontFromAttrs
     *
     * @param familyName familyName
     * @param font font
     * @param styleIndex styleIndex
     * @return Font
     */
    public static Font setFontFromAttrs(
            @Nullable String familyName, Font font,
            @Range(from = 0, to = Constants.NUM_2) int styleIndex) {
        Font tf;
        if (familyName != null) {
            tf = new Font.Builder(familyName).build();
            if (tf != null) {
                return tf;
            }
        }
        Font.Builder builder;
        if (font != null) {
            builder = new Font.Builder(font.getName());
        } else {
            builder = new Font.Builder("");
        }

        if (styleIndex == 1) {
            builder.setWeight(Font.BOLD);
        } else if (styleIndex == Constants.NUM_2) {
            builder.makeItalic(true);
        }
        tf = builder.build();
        return tf;
    }

    /**
     * parseTintMode
     *
     * @param defaultMode defaultMode
     * @param value value
     * @return BlendMode
     */
    @Nullable
    public static BlendMode parseTintMode(int value, @Nullable BlendMode defaultMode) {
        switch (value) {
            case Constants.NUM_3: return BlendMode.SRC_OVER;
            case Constants.NUM_5: return BlendMode.SRC_IN;
            case Constants.NUM_9: return BlendMode.SRC_ATOP;
            case Constants.NUM_14: return BlendMode.MULTIPLY;
            case Constants.NUM_15: return BlendMode.SCREEN;
            case Constants.NUM_16: return BlendMode.valueOf("ADD");
            default: return defaultMode;
        }
    }

    /**
     * Gets the absolute text alignment value based on the supplied gravity and the activities
     * layout direction.
     *
     * @param resources resources
     * @param gravity The gravity to convert to absolute values
     * @param text text
     * @return absolute layout direction
     */
    public static int getTextAlignment(
            final ResourceManager resources,
            final int gravity,
            @Nullable final CharSequence text) {
        return LayoutAlignment.CENTER;
    }

    /**
     * Creates a static text layout. Uses the {@link SimpleTextLayout} if
     * available.
     *
     * @param text The text to be laid out, optionally with spans
     * @param paint The base paint used for layout
     * @param maxTextWidth The width in pixels
     * @param textAlignment Alignment for the resulting {@link SimpleTextLayout}
     * @param alphaModifier The modification to apply to the alpha value between 0 and 1.
     * @return the newly constructed {@link SimpleTextLayout} object
     */
    public static SimpleTextLayout createStaticTextLayout(
            final CharSequence text, final Paint paint,
            final int maxTextWidth, final int textAlignment,
            final float alphaModifier) {
        paint.setAlpha(alphaModifier);
        final SimpleTextLayout layout = new SimpleTextLayout(text.toString(), paint, new Rect(), maxTextWidth);
        return layout;
    }

    /**
     * Scales a rectangle.
     *
     * @param origin The point to scale from.
     * @param base The rectangle at scale 1.0.
     * @param out The rectangle to put the scaled size in.
     * @param scale The amount to scale the rectangle by.
     * @param isEven Should the rectangle be scaled evenly in both directions.
     */
    public static void scale(
            final Point origin,
            RectFloat base, RectFloat out,
            final float scale, final boolean isEven) {
        if (scale == 1) {
            out.modify(base);
            return;
        }

        final float horizontalFromCentre = base.getCenter().getPointX() - base.left;
        final float verticalFromCentre = base.getCenter().getPointY() - base.top;

        if (isEven && scale > 1) {
            final float minChange = Math.min(horizontalFromCentre * scale - horizontalFromCentre,
                    verticalFromCentre * scale - verticalFromCentre);
            out.left = base.left - minChange;
            out.top = base.top - minChange;
            out.right = base.right + minChange;
            out.bottom = base.bottom + minChange;
        } else {
            out.left = origin.getPointX()
                    - horizontalFromCentre * scale * ((origin.getPointX() - base.left) / horizontalFromCentre);
            out.top = origin.getPointY()
                    - verticalFromCentre * scale * ((origin.getPointY() - base.top) / verticalFromCentre);
            out.right = origin.getPointX()
                    + horizontalFromCentre * scale * ((base.right - origin.getPointX()) / horizontalFromCentre);
            out.bottom = origin.getPointY()
                    + verticalFromCentre * scale * ((base.bottom - origin.getPointY()) / verticalFromCentre);
        }
    }

    /**
     * Determines if the text in the supplied layout is displayed right to left.
     *
     * @param layout The layout to check.
     * @param resources resources
     * @return True if the text in the supplied layout is displayed right to left. False otherwise.
     */
    public static boolean isRtlText(@Nullable final Layout layout, final ResourceManager resources) {
        return false;
    }

    /**
     * Calculates the maximum width that the prompt can be.
     *
     * @param maxTextWidth maxTextWidth
     * @param clipBounds clipBounds
     * @param parentWidth parentWidth
     * @param textPadding textPadding
     * @return Maximum width in pixels that the prompt can be.
     */
    public static float calculateMaxWidth(
            final float maxTextWidth,
            @Nullable final Rect clipBounds,
            final int parentWidth,
            final float textPadding) {
        return Math.max(Constants.NUM_80, Math.min(maxTextWidth,
                (clipBounds != null ? clipBounds.right - clipBounds.left : parentWidth)
                        - (textPadding * Constants.NUM_2)));
    }

    /**
     * Calculates the maximum width line in a text layout.
     *
     * @param textLayout The text layout
     * @return The maximum length line
     */
    public static float calculateMaxTextWidth(@Nullable final Layout textLayout) {
        float maxTextWidth = 0f;
        if (textLayout != null) {
            for (int i = 0, count = textLayout.getLineCount(); i < count; i++) {
                maxTextWidth = Math.max(maxTextWidth, textLayout.getLimitWidth(i));
            }
        }
        return maxTextWidth;
    }

    /**
     * Determines if a point is within a rectangle that has been inset.
     *
     * @param bounds The rectangle bounds.
     * @param inset The amount that the rectangle is inset by.
     * @param x The point x coordinate.
     * @param y The point y coordinate.
     * @return True if the point is within the inset rectangle, false otherwise.
     */
    public static boolean containsInset(
            final Rect bounds,
            final int inset,
            final int x,
            final int y) {
        return x > bounds.left + inset
                && x < bounds.right - inset
                && y > bounds.top + inset
                && y < bounds.bottom - inset;
    }
}
